'use strict';

const mysql = require('mysql');
const columnFilter = require('../models/models');

const helper = {};

const POOL = {};
let APP;
let DEFAULT_DATASOURCE;

let inited = false;

class FormatHelper {
  constructor(stringifyObjects = false, timezone = 'local') {
    this.stringifyObjects = stringifyObjects;
    this.timezone = timezone;
  }
  formatWhere(where) {
    let r = this.formatWhereToArray(where);
    r = r.join(' AND ');
    return r;
  }
  formatWhereToArray(where) {
    const keys = Object.keys(where);
    const Symbols = Object.getOwnPropertySymbols(where);
    const result = [];
    for (const key of keys) { // 非symbol字段
      if (Array.isArray(where[key])) {
        const v = [];
        for (let j = 0; j < where[key].length; j++) {
          v.push(this.convertType(where[key][j]));
        }
        result.push(`${key} IN (${v.join()})`);
      } else if (where[key] == null) {
        result.push(`${key} IS NULL`);
      } else {
        const v = this.convertType(where[key]);
        result.push(`${key} = ${v}`);
      }
    }
    for (const s of Symbols) { // symbol字段
      result.push(this.formatWhereSymbol(s, where[s]));
    }
    return result;
  }
  formatWhereSymbol(sym, obj) {
    const des = sym.toString();
    if (des.includes('OR')) return this.formatWhereOr(obj);
    if (des.includes('AND')) return this.formatWhereAnd(obj);
    if (des.includes('>=')) return this.formatWhereGte(obj);
    if (des.includes('>')) return this.formatWhereGt(obj);
    if (des.includes('<=')) return this.formatWhereLte(obj);
    if (des.includes('<')) return this.formatWhereLt(obj);
    if (des.includes('LIKE')) return this.formatWhereLike(obj);
    if (des.includes('NOTNULL')) return this.formatWhereNotNull(obj);
  }
  formatWhereAnd(obj) {
    let r = this.formatWhereToArray(obj);
    r = r.join(' AND ');
    return `(${r})`;
  }
  formatWhereOr(obj) {
    let r = this.formatWhereToArray(obj);
    r = r.join(' OR ');
    return `(${r})`;
  }
  formatWhereGt(obj) {
    let r = [];
    for (const key in obj) {
      const v = this.convertType(obj[key]);
      r.push(`${key} > ${v}`);
    }
    r = r.join(' AND ');
    return r;
  }
  formatWhereGte(obj) {
    let r = [];
    for (const key in obj) {
      const v = this.convertType(obj[key]);
      r.push(`${key} >= ${v}`);
    }
    r = r.join(' AND ');
    return r;
  }
  formatWhereLt(obj) {
    let r = [];
    for (const key in obj) {
      const v = this.convertType(obj[key]);
      r.push(`${key} < ${v}`);
    }
    r = r.join(' AND ');
    return r;
  }
  formatWhereLte(obj) {
    let r = [];
    for (const key in obj) {
      const v = this.convertType(obj[key]);
      r.push(`${key} <= ${v}`);
    }
    r = r.join(' AND ');
    return r;
  }
  formatWhereLike(obj) {
    let r = [];
    for (const key in obj) {
      const v = this.convertType(obj[key]);
      r.push(`${key} LIKE ${v}`);
    }
    r = r.join(' AND ');
    return r;
  }
  formatWhereNotNull(obj) {
    let r = [];
    if (typeof obj === 'string') {
      r.push(`${obj} IS NOT NULL`);
    } else if (Array.isArray(obj)) {
      for (const o of obj) {
        r.push(`${o} IS NOT NULL`);
      }
    }
    r = r.join(' AND ');
    return r;
  }
  convertType(v) {
    return mysql.escape(v, false, this.timezone);
  }
}

// 不可重复属性 用于where分类
helper.P = {
  get or() {
    return Symbol('OR');
  },
  get and() {
    return Symbol('AND');
  },
  get gt() {
    return Symbol('>');
  },
  get gte() {
    return Symbol('>=');
  },
  get lt() {
    return Symbol('<');
  },
  get lte() {
    return Symbol('<=');
  },
  get like() {
    return Symbol('LIKE');
  },
  get not_null() {
    return Symbol('NOTNULL');
  },
};

const using_connection_counts = {};
/**
 * 初始化
 * @param {Egg.Application} app egg app
 */
helper.init = function(app) {
  if (inited) return;
  APP = app;
  DEFAULT_DATASOURCE = app.config.mysql.default;
  // 初始化连接池
  const dataSources = app.config.mysql.clients;
  for (const name in dataSources) {
    const cfg = dataSources[name];
    const pool = mysql.createPool(cfg);
    using_connection_counts[name] = 0;
    pool.on('enqueue', () => {
      app.logger.info('DataSource Pool: Waiting for available connection ' + name);
    });
    pool.on('acquire', () => {
      using_connection_counts[name]++;
      app.logger.debug(`DataSource [${name}] using connection count:`, using_connection_counts[name]);
    });
    pool.on('release', () => {
      using_connection_counts[name]--;
      app.logger.debug(`DataSource [${name}] using connection count:`, using_connection_counts[name]);
    });
    POOL[name] = pool;
  }
  inited = true;
  helper.DEFAULT_DATASOURCE = DEFAULT_DATASOURCE;
};

/**
 * 解析where对象为where条件语句
 *
 * @param {*} where 条件对象
 * @param {'master'|'mysql2'} ds 数据源
 */
helper.explainWhere = function(where = undefined, ds = undefined) {
  if (
    !where ||
    (Object.keys(where).length < 1 && Object.getOwnPropertySymbols(where).length < 1)
  ) return '';

  let cfg;
  if (!ds) {
    cfg = APP.config.mysql.clients[DEFAULT_DATASOURCE];
  } else {
    cfg = APP.config.mysql.clients[ds];
  }
  const help = new FormatHelper(cfg.timezone);
  return 'WHERE ' + help.formatWhere(where);
};

/**
 * 获取一个链接
 * @param {'master'|'mysql2'} ds 数据源
 *
 * @return {Promise<mysql.PoolConnection>} connection 数据库链接
 */
helper.getConnection = function(ds = undefined) {
  ds = ds || DEFAULT_DATASOURCE;
  const pool = POOL[ds];
  if (!pool) {
    throw new Error('Cannot find datasource [' + ds + ']');
  }

  return new Promise(function(resolve, reject) {
    pool.getConnection(function(err, connection) {
      if (err) {
        reject(err);
      } else {
        resolve(connection);
      }
    });
  });
};

/**
 * 根据model中的colimn来过滤字段
 */
helper.columnFilter = columnFilter;

module.exports = helper;
